Spring AI核心架构解析

用Spring AI写代码很简单,几行就能跑起来。但如果你想用好它,或者遇到问题能快速定位,就得搞清楚底层的架构设计。

这篇文章会带你深入Spring AI的核心组件,理解它们之间的关系和协作方式。

整体架构鸟瞰

先从宏观视角看看Spring AI的分层设计:

2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/06-SpringAI详细解析/assets/Fmq6fj54ah6AYVXMZXpht5Svf0lA-e828bab8

Spring AI采用了经典的分层架构,核心思想是面向接口编程。你的业务代码只需要和ChatClient或ChatModel打交道,完全不用关心底层对接的是哪家模型。

Spring AI 的核心是面向接口编程ChatModel 是统一的模型抽象接口,ChatClient 是更高级的封装入口,业务代码与具体的模型实现完全解耦——换模型只需要换依赖,代码基本不用改。

ChatModel:模型统一抽象

ChatModel是Spring AI最核心的接口,它定义了与对话模型交互的标准方式。

接口设计

来看看它的接口定义:

public interface ChatModel extends Model<Prompt, ChatResponse>, StreamingChatModel {

    // 最简单的调用方式:传入字符串,返回字符串
    default String call(String message) {
        Prompt prompt = new Prompt(new UserMessage(message));
        Generation generation = call(prompt).getResult();
        return (generation != null) ? generation.getOutput().getText() : "";
    }

    // 传入多条消息
    default String call(Message... messages) {
        Prompt prompt = new Prompt(Arrays.asList(messages));
        Generation generation = call(prompt).getResult();
        return (generation != null) ? generation.getOutput().getText() : "";
    }

    // 核心方法:传入Prompt,返回完整响应
    @Override
    ChatResponse call(Prompt prompt);

    // 流式调用
    default Flux<ChatResponse> stream(Prompt prompt) {
        throw new UnsupportedOperationException("streaming is not supported");
    }
}

注意到它继承了两个接口:

  • Model<Prompt, ChatResponse>:定义了基本的call方法
  • StreamingChatModel:定义了stream方法,支持流式输出

为什么这样设计?

这种设计带来的好处是解耦。假设今天用阿里云的通义千问,明天要换成OpenAI的GPT-4,你只需要换一个依赖包,业务代码一行不改:

// 业务代码完全不关心用的是哪个模型
@Service
public class ProductService {

    private final ChatModel chatModel;  // 只依赖接口

    public String generateDescription(String productName) {
        return chatModel.call("为商品'" + productName + "'写一段吸引人的描述");
    }
}

ChatClient:更友好的门面

ChatModel虽然功能完整,但用起来稍显繁琐。Spring AI又封装了一个ChatClient,提供了更流畅的API。

ChatClient vs ChatModel

打个比方:ChatModel像是JDBC,功能强大但用起来啰嗦;ChatClient像是Spring Data JPA,简洁优雅,大多数场景用它就够了。

日常开发中优先使用 ChatClient,它提供了更简洁的链式 API,内置了对 Advisor、默认参数的支持。只有当你需要精细控制底层请求(比如自定义 Prompt 构建),才有必要直接使用 ChatModel

看看两种方式的代码对比:

// 使用ChatModel
Prompt prompt = new Prompt(
    List.of(
        new SystemMessage("你是订单分析助手"),
        new UserMessage("分析这个订单的状态")
    ),
    DashScopeChatOptions.builder().withModel("qwen-plus").build()
);
ChatResponse response = chatModel.call(prompt);
String result = response.getResult().getOutput().getText();

// 使用ChatClient - 链式调用,清爽多了
String result = chatClient.prompt()
    .system("你是订单分析助手")
    .user("分析这个订单的状态")
    .options(DashScopeChatOptions.builder().withModel("qwen-plus").build())
    .call()
    .content();

ChatClient的构建方式

ChatClient通过Builder模式构建,可以预设各种默认配置:

@Bean
public ChatClient chatClient(ChatModel chatModel) {
    return ChatClient.builder(chatModel)
            // 默认系统提示词
            .defaultSystem("你是一个专业的电商客服")
            // 默认模型参数
            .defaultOptions(
                DashScopeChatOptions.builder()
                    .withTemperature(0.7)
                    .build()
            )
            // 默认Advisor(拦截器)
            .defaultAdvisors(
                new SimpleLoggerAdvisor()
            )
            .build();
}

这些默认配置在每次调用时都会生效,但也可以在调用时覆盖。

Prompt:请求的载体

Prompt是发给大模型的请求对象,包含了对话内容和调用参数。

Prompt的结构

public class Prompt implements ModelRequest<List<Message>> {

    // 对话内容:历史消息 + 当前消息
    private final List<Message> messages;

    // 调用参数:模型名称、温度值等
    @Nullable
    private ChatOptions chatOptions;
}

一个典型的Prompt长这样:

2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/06-SpringAI详细解析/assets/FhPox-3ypv-6YEFRtm_s9oqUMyhW-d31d0f08

Message的类型

Message表示对话中的一条消息,根据角色不同有四种类型:

类型 说明 使用场景
SystemMessage 系统设定 定义AI的角色、行为准则
UserMessage 用户输入 用户的问题或指令
AssistantMessage AI回复 模型之前的回答
ToolResponseMessage 工具返回 工具调用的结果

在多轮对话中,Message列表会包含完整的对话历史,这就是大模型"记住"上下文的方式。

类型 说明 使用场景
SystemMessage 系统设定 定义AI的角色、行为准则
UserMessage 用户输入 用户的问题或指令
AssistantMessage AI回复 模型之前的回答
ToolResponseMessage 工具返回 工具调用的结果

多轮对话中,Message 列表需要包含完整的对话历史,大模型才能"理解"上下文。

ChatOptions:参数配置

ChatOptions用来设置调用大模型时的各种参数,不同模型厂商支持的参数有差异。

通用参数

// 使用通用的ChatOptions
ChatOptions options = ChatOptions.builder()
    .model("qwen-plus")           // 模型名称
    .temperature(0.7)             // 温度:越高越有创意
    .maxTokens(2000)              // 最大生成token数
    .topP(0.9)                    // 核采样概率
    .build();

厂商特有参数

如果要用某个厂商的特殊参数,需要用对应的Options实现类:

// 阿里云DashScope特有参数
DashScopeChatOptions options = DashScopeChatOptions.builder()
    .withModel("qwen-plus")
    .withTemperature(0.7)
    .withEnableSearch(true)    // 开启联网搜索
    .withResultFormat("text")  // 结果格式
    .build();

参数优先级

参数可以在多个地方设置,优先级从高到低是:

  • 单次调用时指定 - chatClient.prompt().options(xxx) — 最高优先级
  • ChatClient构建时的默认值 - ChatClient.builder().defaultOptions(xxx)
  • 配置文件 - spring.ai.xxx.chat.options.xxx
  • 框架默认值 — 最低优先级

高优先级的配置会覆盖低优先级,灵活地利用这个机制可以让你的配置更整洁。

2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/06-SpringAI详细解析/assets/Fv-gqUcT84gBOKvn8dTY8tQdUUZU-9038bce3

ChatResponse:响应解析

大模型返回的结果封装在ChatResponse中,结构稍微有点复杂。

响应结构

// 简化后的响应结构
public class ChatResponse {

    private ChatResponseMetadata metadata;      // 元数据
    private List<Generation> results;           // 生成结果(可能有多个)

    public Generation getResult() {
        return results.get(0);                  // 获取第一个结果
    }
}

public class Generation {
    private AssistantMessage output;            // AI回复内容
    private ChatGenerationMetadata metadata;    // 生成元数据
}

常用的取值方式

ChatResponse response = chatModel.call(prompt);

// 获取文本内容 - 最常用
String text = response.getResult().getOutput().getText();

// 获取完整的消息对象
AssistantMessage message = response.getResult().getOutput();

// 获取token使用情况
Usage usage = response.getMetadata().getUsage();
System.out.println("输入token: " + usage.getPromptTokens());
System.out.println("输出token: " + usage.getCompletionTokens());
System.out.println("总计token: " + usage.getTotalTokens());

如果用ChatClient,取值更简单:

// 直接拿到文本
String content = chatClient.prompt("你好").call().content();

// 拿到完整响应(需要时)
ChatResponse response = chatClient.prompt("你好").call().chatResponse();

自动配置机制

Spring AI大量使用了Spring Boot的自动配置特性,让集成变得非常简单。

以阿里云DashScope为例,当你引入spring-ai-alibaba-starter-dashscope依赖后,会自动发生以下事情:

2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/06-SpringAI详细解析/assets/Fv9d3XN53yLLZ7x5K_8YF-4Dd_DZ-e63d550d

这一切都是自动完成的,你只需要配置API Key就行了。

Spring AI 大量借助 Spring Boot AutoConfiguration,引入 starter 依赖并配置 API Key 后,ChatModel Bean 会自动注册到容器,可直接注入使用,无需任何手动配置。

自定义Bean

如果默认配置不满足需求,可以自己定义Bean覆盖:

@Configuration
public class CustomAiConfig {

    @Bean
    public ChatModel customChatModel(DashScopeApi dashScopeApi) {
        // 自定义ChatModel配置
        return new DashScopeChatModel(dashScopeApi,
            DashScopeChatOptions.builder()
                .withModel("qwen-max")
                .withTemperature(0.5)
                .build());
    }
}

调用流程全景

最后,我们把整个调用流程串起来看:

2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/06-SpringAI详细解析/assets/Fld1QGkdadyyUI0HNGgoYmBs0RY9-7ab637a9

小结

这篇文章梳理了Spring AI的核心架构:

  • ChatModel是模型抽象层,定义了统一的交互接口
  • ChatClient是更高级的封装,提供流畅的链式API
  • Prompt承载请求信息,包含Message列表和ChatOptions
  • ChatResponse是响应对象,通过getResult().getOutput().getText()获取文本

理解了这些核心概念,你就能更好地使用Spring AI,也能在遇到问题时快速定位根因。

下一篇,我们来聊聊流式输出的实现原理,看看那个Flux到底是怎么回事。


企业级项目导航:⬅️ 03-Spring AI快速入门实战 | 04-Spring AI核心架构解析 | ➡️ 05-响应式流式输出详解